﻿using Alternet.Scripter;
using Alternet.Scripter.Debugger;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting.Channels;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ScriptRunner
{
    public partial class HideForm : Form, IScriptRemoteControl
    {
        private ScriptFinishedDelegate onScriptFinished;
        private string ipcPortName = null;
        private string ipcObjectUri = null;
        private volatile bool scriptRunning;
        private object scriptObj = null;

        private IChannel channel;
        public HideForm(string PortName, string ObjectUri)
        {
            this.Hide();
            this.ShowInTaskbar = false;
            this.ipcPortName = PortName;
            this.ipcObjectUri = ObjectUri;

            this.Text = $"ScriptRunner ipcPortName= {ipcPortName} ipcObjectUri={ipcObjectUri}";

            InitializeComponent();
            this.Load += Form1_Load;
            channel = DebuggerCommunication.StartServer(this, ipcPortName, ipcObjectUri);
            scriptRun.ScriptLanguage = ScriptLanguage.CSharp;
            scriptRun.ScriptMode = ScriptMode.Debug;
            scriptRun.ScriptHost.GenerateModulesOnDisk = true;
            scriptRun.ScriptHost.ModulesDirectoryPath = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), "ScriptDebuggerModules");

            scriptRun.ScriptSource.WithDefaultReferences();

            //scriptRun.ScriptSource.References.Add(typeof(MainForm).Assembly.Location);

            scriptRun.AssemblyKind = ScriptAssemblyKind.DynamicLibrary;
            ClearTempDirectory();

            this.FormClosed += (s, e) => { Application.Exit(); };
        }

        private void Form1_Load(object sender, EventArgs e)
        {

            ScriptCompile();
        }

        public void ScriptCompile()
        {
            string scriptPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DebuggerTest.cs");//Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DebuggerTest.cs");
            scriptRun.ScriptSource.FromScriptFile(scriptPath);
            if (!CompileScriptIfNeeded())
            {
                MessageBox.Show(string.Join("\r\n", scriptRun.ScriptHost.CompilerErrors.Select(x => x.ToString()).ToArray()));
                return;
            }
        }

        public void CompileScript(ScriptCompiledDelegate onScriptCompiled)
        {
            BeginInvoke((Action)(() =>
            {
                var success = CompileScriptIfNeeded();

                InvokeOnScriptCompiled(
                    onScriptCompiled,
                    new ScriptCompilationResult
                    {
                        IsSuccessful = success,
                        TargetAssemblyName = scriptRun.ScriptHost.ExecutableModulePath,
                        Errors = scriptRun.ScriptHost.CompilerErrors,
                    });
            }));
        }

        private Type FindDataBaseType(Assembly assembly, string baseTypeName) => assembly.GetTypes().FirstOrDefault(t => !t.IsAbstract && t.IsClass && t.BaseType != null && t.BaseType.Name == baseTypeName);

        private void InvokeOnScriptCompiled(ScriptCompiledDelegate onScriptCompiled, ScriptCompilationResult result)
        {
            if (onScriptCompiled != null)
            {
                try
                {
                    onScriptCompiled(result);
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.ToString());
                }
            }
        }

        public bool IsScriptRunning()
        {
            return scriptRunning;
        }

        public void StartScript(ScriptFinishedDelegate onScriptFinished)
        {
            this.onScriptFinished = onScriptFinished;
            BeginInvoke((Action)(() =>
            {
                if (!CompileScriptIfNeeded())
                {
                    MessageBox.Show(string.Join("\r\n", scriptRun.ScriptHost.CompilerErrors.Select(x => x.ToString()).ToArray()));
                    return;
                }
                scriptRunning = true;
                //updateTimer.Start();
            }));
        }

        public void StopScript()
        {
            BeginInvoke((Action)(() =>
            {
                scriptRunning = false;
                //updateTimer.Stop();
                if (onScriptFinished != null)
                    InvokeOnScriptFinished(onScriptFinished);
                onScriptFinished = null;
            }));
            if (channel != null)
            {
                DebuggerCommunication.StopServer(channel);
                channel = null;
                GC.Collect();
            }
            this.Close();
        }

        private bool CompileScriptIfNeeded()
        {
            if (!scriptRun.Compiled)
            {
                scriptRun.ScriptHost.AssemblyFileName = Guid.NewGuid().ToString("N") + ".dll";
                return scriptRun.Compile();
            }

            return true;
        }

        private void InvokeOnScriptFinished(ScriptFinishedDelegate onScriptFinished)
        {
            if (onScriptFinished != null)
            {
                try
                {
                    onScriptFinished();
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.ToString());
                }
            }
        }
        private void ClearTempDirectory()
        {
            try
            {
                var path = scriptRun.ScriptHost.ModulesDirectoryPath;
                if (Directory.Exists(path))
                {
                    var directory = new DirectoryInfo(path);
                    foreach (System.IO.FileInfo file in directory.GetFiles())
                    {
                        try
                        {
                            file.Delete();
                        }
                        catch
                        {
                        }
                    }
                }
            }
            catch
            {
            }
        }

        public void RunMethod()
        {
            Assembly assembly = scriptRun.ScriptHost.ScriptAssembly;

            if (assembly == null)
            {
                MessageBox.Show("assembly is null");
                return;
            }

            //scriptRun.RunMethod("Test");
            Type type = FindDataBaseType(assembly, "DataBase");
            scriptObj = Activator.CreateInstance(type);
            type.BaseType.InvokeMember("Init", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod, null, scriptObj, new object[] { "ExampleName" });
        }

        private void button2_Click(object sender, EventArgs e)
        {
            RunMethod();
        }
    }
}
